﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Dynamics.BusinessConnectorNet;
using System.Globalization;
using PJ.Data.DynamicsAX;
using System.Collections;

namespace PJ.Data
{
    /// <summary>
    /// Dynamics AX server implementation of <see cref="IObjectDataProvider"> persistence interface. 
    /// </summary>
    /// <typeparam name="TDataInterface">Object data interface type</typeparam>
    /// <typeparam name="TDataConcreteClass">Object data concrete class type</typeparam>
    public abstract class ObjectAXProvider<TIObjectData> : IObjectProvider<TIObjectData>
        where TIObjectData : class, IObjectData
    {
        #region Constructors

        protected ObjectAXProvider(IObjectDataCache<TIObjectData> cache)
        {
            if (cache == null)
                throw new ArgumentNullException("cache");
            _Cache = cache;
        }
        #endregion

        #region Interface implementation

        public abstract TIObjectData CreateNewItem();

        public virtual bool Exist(string globalId)
        {
            throw new NotImplementedException();
        }

        public virtual void Delete(string globalId)
        {
            throw new NotImplementedException();
        }

        public virtual TIObjectData GetData(string globalId)
        {
            TIObjectData t = Find(globalId);
            if (t == null)
                throw new Data.DataNotFoundException();
            else
                return t;
        }


        public virtual TIObjectData Find(string globalId)
        {
            int tableId;
            Int64 recordId;

            GetAxRecordInfo(globalId, out tableId, out recordId);
            using (AxaptaContainer container = (AxaptaContainer)CallAXStaticMethod("Find", recordId))
            {
                if (container.Count != 0)
                {
                    TIObjectData data = CreateNewItem(container);
                    if (Cache != null)
                        Cache.Add(data);
                    return data;
                }
                else
                    return null;
            }
        }

        public virtual TIObjectData CachedGet(string globalId)
        {
            TIObjectData t = CachedFind(globalId);
            if (t == null)
                throw new DataNotFoundException();
            else
                return t;
        }

        public virtual TIObjectData CachedFind(string globalId)
        {
            TIObjectData t;
            if (Cache != null)
                t = Cache.Find(globalId) as TIObjectData;
            else
                t = null;
            if (t == null)
                t = Find(globalId);
            return t;
        }

        public virtual void Persist(IEnumerable list)
        {
            if (list == null)
                throw new ArgumentNullException("list");
            bool empty = true;
            foreach (object data in list)
            {
                Persist((TIObjectData)data);
                if (empty == true)
                    empty = false;
            }
            if (empty)
                throw new ArgumentException("list is empty");
        }

        public virtual void Persist(TIObjectData data)
        {
            CallAXStaticMethod("Persist", CopyToContainer(data));
        }

        public virtual void Persist(IObjectData data)
        {
            Persist((TIObjectData)data);
        }

        public virtual TIObjectData PersistAndGet(TIObjectData data)
        {
            using (AxaptaContainer container = (AxaptaContainer)CallAXStaticMethod("PersistAndGet", CopyToContainer(data)))
            {
                if (container.Count != 0)
                {
                    CopyFromContainer(container, data);
                    if (Cache != null)
                        Cache.Add(data);
                    return data;
                }
                else
                    throw new DataNotFoundException();
            }
        }
        #endregion

        #region Interface override
        IObjectData IObjectProvider.CreateNewItem()
        {
            return CreateNewItem();
        }

        IObjectData IObjectProvider.GetData(string globalId)
        {
            return GetData(globalId);
        }

        IObjectData IObjectProvider.Find(string globalId)
        {
            return Find(globalId);
        }

        IObjectData IObjectProvider.CachedGet(string globalId)
        {
            return CachedGet(globalId);
        }

        IObjectData IObjectProvider.CachedFind(string globalId)
        {
            return CachedFind(globalId);
        }

        public virtual IObjectData PersistAndGet(IObjectData data)
        {
            return PersistAndGet((TIObjectData)data);
        }

        #endregion

        #region Public


        #endregion

        #region Protected

        protected TIObjectData CreateNewItem(AxaptaContainer container)
        {
            TIObjectData temp = (TIObjectData)CreateNewItem();
            CopyFromContainer(container, temp);
            return temp;
        }

        protected IObjectDataCache<TIObjectData> Cache
        {
            get { return _Cache; }
        }

        protected object CallAXStaticMethod(string methodName, object param)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param);
        }
        protected object CallAXStaticMethod(string methodName, object param1, object param2)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param1, param2);
        }
        protected object CallAXStaticMethod(string methodName, object param1, object param2, object param3)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param1, param2, param3);
        }
        protected object CallAXStaticMethod(string methodName, object[] param)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param);
        }

        protected virtual void CopyFromContainer(AxaptaContainer container, TIObjectData data)
        {
            if (container == null)
                throw new ArgumentNullException("container");
            int count = container.Count;
            if (count < 2)
                throw new ArgumentOutOfRangeException("container", count, "should have at least 2 items");
            int tableId = (int)container.get_Item(1);
            Int64 recordId = (Int64)container.get_Item(2);
            if (recordId == 0 || tableId == 0)
                throw new ArgumentException("second and fird item of container can't be 0 valued");
            data.GlobalId = this.GetType().Name + '@' + Convert.ToString(tableId, CultureInfo.InvariantCulture) + '.' + Convert.ToString(recordId, CultureInfo.InvariantCulture);
        }

        protected virtual AxaptaContainer CopyToContainer(TIObjectData data)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            AxaptaContainer container = DynamicsAXBroker.CreateContainer();
            try
            {
                int tableId;
                Int64 recordId;

                GetAxRecordInfo(data.GlobalId, out tableId, out recordId);
                container.Add(tableId);
                container.Add(recordId);
            }
            catch
            {
                container.Dispose();
                throw;
            }
            return container;
        }

        #endregion

        #region Private


        private string GetDAXClassName()
        {
            return "PJ" + this.GetType().Name;
        }

        private void GetAxRecordInfo(string globalId, out int tableId, out Int64 recordId)
        {
            if (string.IsNullOrEmpty(globalId))
                throw new ArgumentNullException("globalId");
            string[] parts = globalId.Split("@.".ToCharArray(), 3);
            if (parts.Count() != 3)
                throw new ArgumentException("should be \"" + this.GetType().Name + "@AxTableId.AxRecordId\" : " + globalId, "globalId");
            if (parts[0] != this.GetType().Name)
                throw new ArgumentException(parts[0] + " found. Should be " + this.GetType().Name, "globalId");
            tableId = Convert.ToInt32(parts[1], CultureInfo.InvariantCulture);
            if (tableId == 0)
                throw new ArgumentOutOfRangeException("tableId from globalId is 0");
            recordId = Convert.ToInt64(parts[2], CultureInfo.InvariantCulture);
            if (recordId == 0L)
                throw new ArgumentOutOfRangeException("recordId from globalId is 0");
        }

        #endregion

        #region Fields

        private readonly IObjectDataCache<TIObjectData> _Cache;

        #endregion

    }
}
